/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.solr.rest.schema.analysis;

import java.nio.file.Path;
import java.util.Arrays;
import java.util.SortedMap;
import java.util.TreeMap;
import org.apache.commons.io.file.PathUtils;
import org.apache.solr.common.util.Utils;
import org.apache.solr.util.RestTestBase;
import org.eclipse.jetty.ee10.servlet.ServletHolder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

/**
 * Test the REST API for managing stop words, which is pretty basic:
 *
 * <p>GET: returns the list of stop words or a single word if it exists
 *
 * <p>PUT: add some words to the current list
 */
public class TestManagedStopFilterFactory extends RestTestBase {

  private static final String collection = "collection1";
  private static final String confDir = collection + "/conf";

  @Before
  public void before() throws Exception {
    Path tmpSolrHome = createTempDir();
    PathUtils.copyDirectory(TEST_HOME(), tmpSolrHome);

    final SortedMap<ServletHolder, String> extraServlets = new TreeMap<>();

    System.setProperty("managed.schema.mutable", "true");
    System.setProperty("solr.index.updatelog.enabled", "false");

    createJettyAndHarness(
        tmpSolrHome,
        "solrconfig-managed-schema.xml",
        "schema-rest.xml",
        "/solr",
        true,
        extraServlets);
  }

  @After
  public void after() throws Exception {
    solrClientTestRule.reset();
    System.clearProperty("managed.schema.mutable");
    System.clearProperty("solr.index.updatelog.enabled");

    if (restTestHarness != null) {
      restTestHarness.close();
    }
    restTestHarness = null;
  }

  /**
   * Test adding managed stopwords to an endpoint defined in the schema, then adding docs containing
   * a stopword before and after removing the stopword from the managed stopwords set.
   */
  @Test
  public void testManagedStopwords() throws Exception {
    // invalid endpoint
    //// TODO: This returns HTML vs JSON because the exception is thrown
    ////       from the init method of ManagedEndpoint ... need a better solution
    // assertJQ("/schema/analysis/stopwords/bogus", "/error/code==404");

    // this endpoint depends on at least one field type containing the following
    // declaration in the schema-rest.xml:
    //
    //   <filter class="solr.ManagedStopFilterFactory" managed="english" />
    //
    String endpoint = "/schema/analysis/stopwords/english";

    // test the initial GET request returns the default stopwords settings
    assertJQ(endpoint, "/wordSet/initArgs/ignoreCase==false", "/wordSet/managedList==[]");

    // add some stopwords and verify they were added
    assertJPut(
        endpoint, Utils.toJSONString(Arrays.asList("a", "an", "the")), "/responseHeader/status==0");

    // test requesting a specific stop word that exists / does not exist
    assertJQ(endpoint + "/the", "/the=='the'");
    // not exist - 404
    assertJQ(endpoint + "/foo", "/error/code==404");
    // wrong case - 404
    assertJQ(endpoint + "/An", "/error/code==404");

    // update the ignoreCase initArg to true and make sure case is ignored
    String updateIgnoreCase =
        "{ 'initArgs':{ 'ignoreCase':true }, "
            + "'managedList':['A','a','AN','an','THE','the','of','OF'] }";
    assertJPut(endpoint, json(updateIgnoreCase), "/responseHeader/status==0");

    assertJQ(
        endpoint,
        "/wordSet/initArgs/ignoreCase==true",
        "/wordSet/managedList==['a','an','of','the']");

    // verify ignoreCase applies when requesting a word
    assertJQ("/schema/analysis/stopwords/english/The", "/The=='the'");

    // verify the resource supports XML writer type (wt) as well as JSON
    assertQ(
        endpoint,
        "count(/response/lst[@name='wordSet']/arr[@name='managedList']/*) = 4",
        "(/response/lst[@name='wordSet']/arr[@name='managedList']/str)[1] = 'a'",
        "(/response/lst[@name='wordSet']/arr[@name='managedList']/str)[2] = 'an'",
        "(/response/lst[@name='wordSet']/arr[@name='managedList']/str)[3] = 'of'",
        "(/response/lst[@name='wordSet']/arr[@name='managedList']/str)[4] = 'the'");

    restTestHarness.reload(); // make the word set available

    String newFieldName = "managed_en_field";
    // make sure the new field doesn't already exist
    assertQ(
        "/schema/fields/" + newFieldName + "?indent=on&wt=xml",
        "count(/response/lst[@name='field']) = 0",
        "/response/lst[@name='responseHeader']/int[@name='status'] = '404'",
        "/response/lst[@name='error']/int[@name='code'] = '404'");

    // add the new field
    assertJPost(
        "/schema/fields",
        "{add-field : { name :managed_en_field, type : managed_en}}",
        "/responseHeader/status==0");

    // make sure the new field exists now
    assertQ(
        "/schema/fields/" + newFieldName + "?indent=on&wt=xml",
        "count(/response/lst[@name='field']) = 1",
        "/response/lst[@name='responseHeader']/int[@name='status'] = '0'");

    assertU(adoc(newFieldName, "This is the one", "id", "6"));
    assertU(commit());

    assertQ(
        "/select?q=" + newFieldName + ":This",
        "/response/lst[@name='responseHeader']/int[@name='status'] = '0'",
        "/response/result[@name='response'][@numFound='1']",
        "/response/result[@name='response']/doc/str[@name='id'][.='6']");

    assertQ(
        "/select?q=%7B%21raw%20f=" + newFieldName + "%7Dthe",
        "/response/lst[@name='responseHeader']/int[@name='status'] = '0'",
        "/response/result[@name='response'][@numFound='0']");

    // verify delete works
    assertJDelete(endpoint + "/the", "/responseHeader/status==0");

    // verify that removing 'the' is not yet in effect
    assertU(adoc(newFieldName, "This is the other one", "id", "7"));
    assertU(commit());

    assertQ(
        "/select?q=%7B%21raw%20f=" + newFieldName + "%7Dthe",
        "/response/lst[@name='responseHeader']/int[@name='status'] = '0'",
        "/response/result[@name='response'][@numFound='0']");

    restTestHarness.reload();

    // verify that after reloading, removing 'the' has taken effect
    assertU(adoc(newFieldName, "This is the other other one", "id", "8"));
    assertU(commit());

    assertQ(
        "/select?q=%7B%21raw%20f=" + newFieldName + "%7Dthe",
        "/response/lst[@name='responseHeader']/int[@name='status'] = '0'",
        "/response/result[@name='response'][@numFound='1']",
        "/response/result[@name='response']/doc/str[@name='id'][.='8']");

    assertJQ(
        endpoint, "/wordSet/initArgs/ignoreCase==true", "/wordSet/managedList==['a','an','of']");

    // should fail with 404 as foo doesn't exist
    assertJDelete(endpoint + "/foo", "/error/code==404");

    // test for SOLR-6853 - should be able to delete stopwords with slash
    assertJPut(
        endpoint,
        Utils.toJSONString(Arrays.asList("cheerful/joyful", "sleepy/tired")),
        "/responseHeader/status==0");

    // verify delete works
    assertJDelete(endpoint + "/cheerful/joyful", "/responseHeader/status==0");

    // should fail with 404 as some/thing doesn't exist
    assertJDelete(endpoint + "/cheerful/joyful", "/error/code==404");
  }

  /** Can we add and remove stopwords with umlauts */
  @Test
  public void testCanHandleDecodingAndEncodingForStopwords() throws Exception {
    String endpoint = "/schema/analysis/stopwords/german";

    // initially it should not exist
    assertJQ(endpoint + "/schön", "/error/code==404");

    // now we put a stopword with an umlaut
    assertJPut(endpoint, Utils.toJSONString(Arrays.asList("schön")), "/responseHeader/status==0");

    // let's check if it exists
    assertJQ(endpoint + "/schön", "/schön=='schön'");

    // now let's remove it
    assertJDelete(endpoint + "/schön", "/responseHeader/status==0");

    // and of it is unavailable again
    assertJQ(endpoint + "/schön", "/error/code==404");
  }
}
